using System.ComponentModel;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

using TInput = Microsoft.Xna.Framework.Content.Pipeline.Graphics.Texture2DContent;
using TOutput = Microsoft.Xna.Framework.Content.Pipeline.Processors.ModelContent;
using LoA.Shared.Interfaces;
using LoA.Content.Pipeline;
using LoA.Shared;

namespace LoA.Content.Pipeline {
	[ContentProcessor(DisplayName = "Model of the Map - LoA Framework")]
	public class MapModelProcessor : ContentProcessor<TInput, TOutput> {
		#region Processor Parameters
		/// <summary>
		/// Controls the scale of the terrain. This will be the distance between
		/// vertices in the finished terrain mesh.
		/// </summary>
		[DefaultValue(2.0f)]
		[Description("The distance between vertices in the finished terrain mesh.")]
		public float Scale {
			get { return scale; }
			set { scale = value; }
		}
		private float scale = 2.0f;

		/// <summary>
		/// Controls the how often the texture applied to the terrain will be repeated.
		/// </summary>
		[DefaultValue(1.0f)]
		[Description("Controls how often the texture will be repeated across the terrain.")]
		public float TexCoordScale {
			get { return texCoordScale; }
			set { texCoordScale = value; }
		}
		private float texCoordScale = 1.0f;

		/// <summary>
		/// Controls the texture that will be applied to the terrain. If no value is
		/// supplied, a texture will not be applied.
		/// </summary>
		[DefaultValue(@"..\rocks.bmp")]
		[Description("Controls the texture that will be applied to the terrain. If no value is supplied, a texture will not be applied.")]
		[DisplayName("Terrain Texture")]
		public string TerrainTexture {
			get { return terrainTexture; }
			set { terrainTexture = value; }
		}
		private string terrainTexture = @"..\rocks.bmp";
		#endregion

		/// <summary>
		/// Generates a terrain mesh from an input heightfield texture.
		/// </summary>
		public override TOutput Process(TInput input, ContentProcessorContext context) {
			//X-Red, Y-Green, Z-Blue, W - Alpha(8-bit=Black, 24-bit=RGB, 32-bit=RGBA)
			PixelBitmapContent<Vector4> heightfield;

			NodeContent root = new NodeContent( );
			MeshBuilder builder = MeshBuilder.StartMesh("terrain");

			// Convert the input texture to float format, for ease of processing.
			input.ConvertBitmapType(typeof(PixelBitmapContent<Vector4>));

			heightfield = (PixelBitmapContent<Vector4>)input.Mipmaps[0];

			float[ ] heightMap = new float[heightfield.Height * heightfield.Width];

			// Create the terrain vertices.
			int i = 0;
			for (int y = 0; y < heightfield.Height; y++) {
				for (int x = 0; x < heightfield.Width; x++) {
					Vector3 position;

					// position the vertices so that the heightfield is centered around x=0, z=0
					position.X = scale * x;
					position.Z = scale * y;

					position.Y = (heightfield.GetPixel(x, y).Y * 256 + heightfield.GetPixel(x, y).X) * 255 - 20000;
					heightMap[i] = (heightfield.GetPixel(x, y).Y * 256 + heightfield.GetPixel(x, y).X) * 255 - 20000;

					builder.CreatePosition(position);
					i++;
				}
			}

			// Create a material, and point it at our terrain texture.
			BasicMaterialContent material = new BasicMaterialContent( );
			material.SpecularColor = new Vector3(.4f, .4f, .4f);

			if (!string.IsNullOrEmpty(TerrainTexture)) {
				string directory = Path.GetDirectoryName(input.Identity.SourceFilename);
				string texture = Path.Combine(directory, TerrainTexture);

				material.Texture = new ExternalReference<TextureContent>(texture);
			}

			builder.SetMaterial(material);

			// Create a vertex channel for holding texture coordinates.
			int texCoordId = builder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));

			// Create the individual triangles that make up our terrain.
			for (int y = 0; y < heightfield.Height - 1; y++) {
				for (int x = 0; x < heightfield.Width - 1; x++) {
					AddVertex(builder, texCoordId, heightfield.Width, x, y);
					AddVertex(builder, texCoordId, heightfield.Width, x + 1, y);
					AddVertex(builder, texCoordId, heightfield.Width, x + 1, y + 1);

					AddVertex(builder, texCoordId, heightfield.Width, x, y);
					AddVertex(builder, texCoordId, heightfield.Width, x + 1, y + 1);
					AddVertex(builder, texCoordId, heightfield.Width, x, y + 1);
				}
			}

			// Chain to the ModelProcessor so it can convert the mesh we just generated.
			MeshContent terrainMesh = builder.FinishMesh( );
			root.Children.Add(terrainMesh);

			ModelContent model = context.Convert<NodeContent, ModelContent>(root, "ModelProcessor");
			string[ ] s = input.Identity.SourceFilename.Replace('\\', '-').Replace(Path.GetExtension(input.Identity.SourceFilename), "").Split('-');

			MapInfo mapInfo = new MapInfo(s[s.Length - 4], new Point(int.Parse(s[s.Length - 2]), int.Parse(s[s.Length - 1])), heightMap, scale, heightfield.Width, heightfield.Height);
			model.Tag = mapInfo;

			return model;
		}

		/// <summary>
		/// Helper for adding a new triangle vertex to a MeshBuilder,
		/// along with an associated texture coordinate value.
		/// </summary>
		void AddVertex(MeshBuilder builder, int texCoordId, int w, int x, int y) {
			builder.SetVertexChannelData(texCoordId, new Vector2(x, y) * TexCoordScale);

			builder.AddTriangleVertex(x + y * w);
		}
	}
}